package com.terrapages.wmsc4j.wms;

import java.awt.Dimension;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import com.terrapages.wmsc4j.bean.BBox;
import com.terrapages.wmsc4j.layer.tile.ImageMimeType;

/**
 * Class for all the url query parameters given in a WMS request.
 * 
 * Provides methods to access/modify the information most useful in
 * relation to WMSC, namly boundingbox and image size.
 * 
 * @author mvivian
 */
public class WMSParameters {
	private static final String BBOX = "bbox";
	private static final String BBOX_COORDS_COUNT_ERROR = "the bounding box requires 4 comma delimited coordinate numbers.";
	private static final String BBOX_COORDS_NUMBER_ERROR = "the bounding box coordinates must be valid numbers.";
	private static final String BBOX_NULL_ERROR = "the bounding box is a required parameter.";
	
	private static final String WIDTH = "width";
	private static final String HEIGHT = "height";
	private static final String WIDTH_HEIGHT_NUMBER_ERROR = "the Width and Height must be integers.";
	private static final String WIDTH_HEIGHT_POSITIVE_ERROR = "the Width and Height must be positive.";
	
	private static final String LAYERS = "layers";
	
	private static final String FORMAT = "format";
	
	private final HashMap<String, List<String>> wmsParameterMap;
	
	private WMSParameters(final HashMap<String, List<String>> wmsParameterMap) {
		super();
		this.wmsParameterMap = wmsParameterMap;
	}

	/**
	 * Gets the bounding box specified of the WMS Request.
	 * 
	 * @return BBox - The Boundingbox of the 
	 * @throws InvalidWMSParameterException if there isn't four numbers or the numbers arn't actually numbers
	 */
	public BBox getBoundingBox() throws InvalidWMSParameterException
	{
		BBox bBox = null;

		String bBoxString = wmsParameterMap.get(BBOX).get(0);
		bBoxString = bBoxString.replaceAll("%2C", ",");
		
		if(bBoxString==null)
		{
			throw new InvalidWMSParameterException(BBOX, BBOX_NULL_ERROR);
		}
		else
		{
			final String[] bBoxStringCoords = bBoxString.split(",");
			if(bBoxStringCoords.length == 4)
			{
				try
				{
					bBox = new BBox(
							Double.parseDouble(bBoxStringCoords[0]),
							Double.parseDouble(bBoxStringCoords[1]),
							Double.parseDouble(bBoxStringCoords[2]),
							Double.parseDouble(bBoxStringCoords[3])
					);
				}
				catch(NumberFormatException NFE)
				{
					throw new InvalidWMSParameterException(BBOX,bBoxString,BBOX_COORDS_NUMBER_ERROR);
				}
			}
			else
			{
				throw new InvalidWMSParameterException(BBOX,bBoxString,BBOX_COORDS_COUNT_ERROR);
			}
		}
		return bBox;
	}
	
	/**
	 * Sets the Bounding box for this WMS request.
	 * @param bbox
	 */
	public void setBoundingBox(final BBox bbox)
	{
		List<String> list = new ArrayList<String>();
		list.add(BBoxToString(bbox));
		wmsParameterMap.remove(BBOX);
		wmsParameterMap.put(BBOX,list);
	}
	
	/**
	 * Gets the image size from the WMS request
	 * @return Dimension
	 * @throws InvalidWMSParameterException if the width or height not positive numbers
	 */
	public Dimension getImageSize() throws InvalidWMSParameterException
	{
		final int width;
		final int height;
		
//		Parse Width
		final String widthString = wmsParameterMap.get(WIDTH).get(0);
		try
		{
			width = Integer.parseInt(widthString);
		}
		catch(NumberFormatException NFE)
		{
			throw new InvalidWMSParameterException(WIDTH,widthString,WIDTH_HEIGHT_NUMBER_ERROR);
		}
		
//		Parse Height
		final String heightString = wmsParameterMap.get(HEIGHT).get(0);
		try
		{
			height = Integer.parseInt(heightString);
		}
		catch(NumberFormatException NFE)
		{
			throw new InvalidWMSParameterException(HEIGHT,heightString,WIDTH_HEIGHT_NUMBER_ERROR);
		}
		
		if (width <= 0)
		{
			throw new InvalidWMSParameterException(WIDTH,widthString,WIDTH_HEIGHT_POSITIVE_ERROR);
		}
		if (height <= 0)
		{
			throw new InvalidWMSParameterException(HEIGHT,heightString,WIDTH_HEIGHT_POSITIVE_ERROR);
		}
		
		return new Dimension(width,height);
	}
	
	/**
	 * Sets the image size.
	 * @param imageSize
	 */
	public void setImageSize(final Dimension imageSize)
	{
		List<String> list = new ArrayList<String>();
		list.add(""+imageSize.width);
		wmsParameterMap.remove(WIDTH);
		wmsParameterMap.put(WIDTH,list);
		
		
		list = new ArrayList<String>();
		list.add(""+imageSize.height);
		wmsParameterMap.remove(HEIGHT);
		wmsParameterMap.put(HEIGHT,list);
	}
	
	/**
	 * Gets the comma delimited String of layers
	 * @return layers, demilited by commas
	 */
	public String getLayers()
	{
		return wmsParameterMap.get(LAYERS).get(0);
	}
	
	/**
	 * Sets the layers for this WMS Parameters
	 * @param layers, delimited by commas
	 */
	public void setLayers(final String layers)
	{
		List<String> list = new ArrayList<String>();
		list.add(layers);
		wmsParameterMap.put(LAYERS,list);
	}
	
	public ImageMimeType getFormat()
	{
		String format = wmsParameterMap.get(FORMAT).get(0);
		return ImageMimeType.valueOf(format.split("/")[1].toUpperCase());
	}
	
	public void setFormat(final ImageMimeType format)
	{
		List<String> list = new ArrayList<String>();
		list.add(format.toString());
		wmsParameterMap.remove(FORMAT);
		wmsParameterMap.put(FORMAT,list);
	}
	
	/**
	 * Builds a request query String that could be appended to the url path to form a complete wms request
	 * 
	 * eg "key1=value1&key2=value2&key3=value3"
	 */
	public String toQuery()
	{
		final StringBuffer retString = new StringBuffer("");
		for(Object key : wmsParameterMap.keySet())
		{
			for(String value : wmsParameterMap.get(key))
			{
				retString.append('&').append(key).append('=').append(value);
			}
		}
		
		return retString.substring(1); //cut off first char which is a "&" due to the nature of the algorithm.
	}
	
	/**
	 * Creates a new WMSParameters object by parsing in a query string.
	 * The query string must be in the folowing format<br>
	 * "key1=value1&key2=value2&key3=value3"
	 * <p>
	 * DO NOT INCLUDE THE URL PART <br>
	 * ie "http://mywmsc.mydomain.com/wms?"<br>
	 *  THIS WILL NOT WORK! Just the parameters</p>
	 */
	public static WMSParameters parseQuery(final String query)
	{
		final HashMap<String, List<String>> retMap = new HashMap<String, List<String>>();
		
		final String[] queryKVPs = query.split("&");
		for(String kvp: queryKVPs)
		{
			final int equalsIndex = kvp.indexOf("=");
			final String key = kvp.substring(0, equalsIndex).toLowerCase();
			final String value = kvp.substring(equalsIndex+1);
			
			List<String> values = retMap.get(key);
			
			if(values == null)
			{
				values = new ArrayList<String>();
				retMap.put(key, values);
			}
			values.add(value);
		}
		
		return new WMSParameters(retMap);
	}
	
	/**
	 * Private method to turn a BBox into its wms query string representation
	 * @param bbox
	 * @return
	 */
	private static String BBoxToString(final BBox bbox)
	{
		return 
		bbox.getMinLongitude() + "," + 
		bbox.getMinLatitude()  + "," +
		bbox.getMaxLongitude() + "," + 
		bbox.getMaxLatitude();
	}
	
	public WMSParameters clone()
	{
		return new WMSParameters(new HashMap<String,List<String>>(wmsParameterMap));
	}
}
